#include "gtest/gtest.h"
#include "spdlog/spdlog.h"
#include "STL_thread.h"
#include "mock/task_queue_mock.cpp"

TEST(STL_thread_test, base) {
    std::function<void()> run = [] {
        spdlog::info("STL_thread_test.base");
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    };
    simpleThread::TaskQueueMock queueMock;
    EXPECT_CALL(queueMock, pull()).WillRepeatedly(testing::Return(run));

    simpleThread::STLThread stlThread(&queueMock);
    stlThread.start();
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

TEST(STL_thread_test, join) {
    volatile int n = 0;
    std::function<void()> run = [&n] {
        spdlog::info("STL_thread_test.thread_join");
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        n += 1;
    };
    simpleThread::TaskQueueMock queueMock;
    EXPECT_CALL(queueMock, pull())
            .Times(4)
            .WillOnce(testing::Return(run))
            .WillOnce(testing::Return(run))
            .WillOnce(testing::Return(run))
            .WillRepeatedly(testing::Return(nullptr));
    EXPECT_CALL(queueMock, size()).WillRepeatedly(testing::Return(0L));

    volatile bool isCheck = false;
    std::function<bool(simpleThread::STLThread &)> checker = [&isCheck](simpleThread::STLThread &thread) {
        isCheck = true;
        return true;
    };

    simpleThread::STLThread stlThread(&queueMock);
    stlThread.setChecker(checker);
    stlThread.start();
    stlThread.join();

    EXPECT_EQ(n, 3);
    EXPECT_TRUE(isCheck);
    EXPECT_EQ(stlThread.getState(), simpleThread::ThreadState::close);
}

TEST(STL_thread_test, shutdown) {
    volatile int n = 0;
    std::function<void()> run = [&n] {
        n += 1;
        spdlog::info("STL_thread_test.thread_exit");
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    };
    simpleThread::TaskQueueMock queueMock;
    EXPECT_CALL(queueMock, pull()).WillRepeatedly(testing::Return(run));

    volatile bool isCheck = false;
    std::function<bool(simpleThread::STLThread &)> checker = [&isCheck](simpleThread::STLThread &thread) {
        isCheck = true;
        return true;
    };

    simpleThread::STLThread stlThread(&queueMock);
    stlThread.setChecker(checker);
    stlThread.start();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    stlThread.shutdown();

    spdlog::info("Execution count n={}", n);
    EXPECT_FALSE(isCheck);
    EXPECT_EQ(stlThread.getState(), simpleThread::ThreadState::close);
}

TEST(STL_thread_test, initCall) {
    auto runable = [] { std::this_thread::sleep_for(std::chrono::milliseconds(100)); };
    simpleThread::TaskQueueMock queueMock;
    EXPECT_CALL(queueMock, pull()).WillRepeatedly(testing::Return(runable));

    volatile bool state = false;
    std::function<bool(simpleThread::STLThread &)> initCall = [&state](simpleThread::STLThread &thread) {
        state = true;
        return true;
    };

    simpleThread::STLThread stlThread(&queueMock);
    stlThread.setInitCall(initCall);
    stlThread.start();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    stlThread.shutdown();

    EXPECT_TRUE(state);
    EXPECT_EQ(stlThread.getState(), simpleThread::ThreadState::close);
}

TEST(STL_thread_test, exitCall) {
    auto runable = [] { std::this_thread::sleep_for(std::chrono::milliseconds(100)); };
    simpleThread::TaskQueueMock queueMock;
    EXPECT_CALL(queueMock, pull())
            .Times(4)
            .WillOnce(testing::Return(runable))
            .WillOnce(testing::Return(runable))
            .WillOnce(testing::Return(runable))
            .WillRepeatedly(testing::Return(nullptr));
    EXPECT_CALL(queueMock, size()).WillRepeatedly(testing::Return(0L));

    volatile bool state = false;
    std::function<bool(simpleThread::STLThread &)> exitCall = [&state](simpleThread::STLThread &thread) {
        spdlog::info("STL_thread_test.exitCall");
        state = true;
        return true;
    };

    simpleThread::STLThread stlThread(&queueMock);
    stlThread.setExitCall(exitCall);
    stlThread.start();
    stlThread.join();

    EXPECT_TRUE(state);
    EXPECT_EQ(stlThread.getState(), simpleThread::ThreadState::close);
}

TEST(STL_thread_test, timeout) {
    auto runable = [] {
        spdlog::info("STL_thread_test.timeout");
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    };
    simpleThread::TaskQueueMock queueMock;
    EXPECT_CALL(queueMock, pull())
            .WillOnce(testing::Return(runable))
            .WillOnce(testing::Return(runable))
            .WillRepeatedly(testing::Return(nullptr));
    EXPECT_CALL(queueMock, size()).WillRepeatedly(testing::Return(0L));

    simpleThread::STLThread stlThread(&queueMock);
    stlThread.start();
    stlThread.timeout();
    std::this_thread::sleep_for(std::chrono::seconds(1));

    EXPECT_EQ(stlThread.getState(), simpleThread::ThreadState::close);
}

TEST(STL_thread_test, timeout_check) {
    auto runable = [] {
        spdlog::info("STL_thread_test.timeout");
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    };
    simpleThread::TaskQueueMock queueMock;
    EXPECT_CALL(queueMock, pull())
            .WillOnce(testing::Return(runable))
            .WillOnce(testing::Return(runable))
            .WillRepeatedly(testing::Return(nullptr));
    EXPECT_CALL(queueMock, size()).WillRepeatedly(testing::Return(0L));

    std::function<bool(simpleThread::STLThread &)> checker = [](simpleThread::STLThread &thread) {
        spdlog::info("STL_thread_test.timeout");
        thread.timeout();
        return true;
    };

    simpleThread::STLThread stlThread(&queueMock);
    stlThread.setChecker(checker);
    stlThread.start();
    std::this_thread::sleep_for(std::chrono::seconds(1));

    EXPECT_EQ(stlThread.getState(), simpleThread::ThreadState::close);
}
